/**
 * @author salim
 */

import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.lang.reflect.Constructor;
import java.net.*;

public class Client extends UnicastRemoteObject implements Client_itf {

	/*
	 * Attributes
	 */

	// Core
	private static Client privateClient;
	private static Server_itf remoteServer;
	private static HashMap<Integer, SharedObject> sharedObjectsMap;

	// Serialization
	private static final long serialVersionUID = 1L;

	/*
	 * Methods
	 */

	public Client() throws RemoteException {
		super();
		if (Debug.is_on)
			System.out.println("Client : Client");
	}

	// /////////////////////////////////////////////////
	// Interface to be used by applications
	// /////////////////////////////////////////////////

	/**
	 * initialization of the client layer
	 * 
	 * + initialize the remote server whose methods will be invoked + initialize
	 * the sharedObjects list + instantiate a private client
	 * 
	 * 
	 * @throws NotBoundException
	 * @throws MalformedURLException
	 * @throws RemoteException
	 * 
	 */
	public static void init() throws RemoteException, MalformedURLException,
			NotBoundException {
		if (Debug.is_on)
			System.out.println("Client : init");

		// get a reference to the remote server
		remoteServer = (Server_itf) Naming
				.lookup("//localhost:8081/remoteServer");
		// initialize the sharedObjects list
		sharedObjectsMap = new HashMap<Integer, SharedObject>();
		// instanciate an invisibe client
		privateClient = new Client();
	}

	/*
	 * lookup in the name server Version 1: Get The shared Objet from the server
	 * without checking if the shared object already exists locally
	 */
	public static synchronized SharedObject lookup(String name)
			throws RemoteException {
		if (Debug.is_on)
			System.out.println("Client : lookup " + name);

		int id = remoteServer.lookup(name);

		if (id == -1)
			return null;

		if (sharedObjectsMap.containsKey(id)) {
			return sharedObjectsMap.get(id);
		} else {
			Object obj = remoteServer.get(id);

			String className = obj.getClass().getName();

			Class stubClass;

			try {
				stubClass = Class.forName(className + "_stub");
			} catch (ClassNotFoundException e) {
				System.err.println("Class " + className + " not found.");
				return null;
			}

			Class[] parameterTypes = new Class[] { Object.class, int.class };

			Constructor constructor = null;

			try {
				constructor = stubClass.getConstructor(parameterTypes);
			} catch (SecurityException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			Object[] initArgs = new Object[] { obj, id };

			SharedObject so = null;

			try {
				so = (SharedObject) (constructor.newInstance(initArgs));
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			sharedObjectsMap.put(id, so);
			return so;
		}
	}

	/**
	 * binding in the name server
	 * 
	 * This method registers the shared object so in the server's namin registry
	 * under the name <name>
	 * 
	 * IMPORTANT: The id of the shared object is updated. And is used to insert
	 * the shared object in the rignt place in the sharedObjectMap
	 * 
	 * @param so
	 * @param name
	 */
	public static void register(String name, SharedObject_itf so)
			throws RemoteException {
		if (Debug.is_on)
			System.out.println("Client : register " + name);

		// create the shared object obj on the server
		// This will override objects of the same name
		int id = remoteServer.create(((SharedObject) so).obj);

		// register the object on the naming registry
		remoteServer.register(name, id);

		// update the id of the shared object on the the Client
		((SharedObject) so).id = id;

		// put the sharedObject in the the sharedObjectMap
		sharedObjectsMap.put(id, (SharedObject) so);
	}

	/**
	 * creation of a shared object returns an instance of SharedObject which
	 * contains the shared object and an non exploitable id (-1) because the
	 * shared object is not yet registered on the server
	 */

	public static SharedObject create(Object o) {
		if (Debug.is_on)
			System.out.println("Client : create");

		String className = o.getClass().getName();
		Class stubClass;

		try {
			stubClass = Class.forName(className + "_stub");
		} catch (ClassNotFoundException e) {
			System.err.println("Class " + className + " not found.");
			return null;
		}

		Class[] parameterTypes = new Class[] { Object.class, int.class };

		Constructor constructor = null;

		try {
			constructor = stubClass.getConstructor(parameterTypes);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		Object[] initArgs = new Object[] { o, -1 };

		SharedObject so = null;

		try {
			so = (SharedObject) (constructor.newInstance(initArgs));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return so;

	}

	/**
	 * Method containKey to know if a sharedObject of the id <id> is in the
	 * sharedObjectsMap of the Client
	 * 
	 * @param id
	 * @return boolean
	 */
	public static boolean containsKey(int id) {
		return sharedObjectsMap.containsKey(id);
	}

	/**
	 * Insert a sharedObject so in the sharedObjecstMap
	 * 
	 * @param id
	 * @param so
	 */
	public static void put(int id, SharedObject so) {
		sharedObjectsMap.put(id, so);
	}

	// ///////////////////////////////////////////////////////////
	// Interface to be used by the consistency protocol
	// //////////////////////////////////////////////////////////

	/**
	 * request a read lock from the server
	 * 
	 * @param id
	 * @return Object
	 */
	public static Object lock_read(int id) {
		if (Debug.is_on)
			System.out.println("Client : lock_read " + id);

		while (true) {
			try {
				return remoteServer.lock_read(id, privateClient);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * request a write lock from the server
	 * 
	 * @param id
	 * @return Object
	 */
	public static Object lock_write(int id) {
		if (Debug.is_on)
			System.out.println("Client : lock_write " + id);

		while (true) {
			try {
				return remoteServer.lock_write(id, privateClient);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * receive a lock reduction request from the server
	 * 
	 * Indirection
	 */
	public Object reduce_lock(int id) throws RemoteException {
		if (Debug.is_on)
			System.out.println("Client : reduce_lock " + id);

		return sharedObjectsMap.get(id).reduce_lock();
	}

	/**
	 * receive a reader invalidation request from the server
	 * 
	 * Indirection
	 */
	public void invalidate_reader(int id) throws RemoteException {
		if (Debug.is_on)
			System.out.println("Client : invalidate_reader " + id);

		sharedObjectsMap.get(id).invalidate_reader();
	}

	/**
	 * receive a writer invalidation request from the server
	 * 
	 * Indirection
	 */
	public Object invalidate_writer(int id) throws RemoteException {
		if (Debug.is_on)
			System.out.println("Client : invalidate_writer " + id);

		return sharedObjectsMap.get(id).invalidate_writer();
	}

	/**
	 * Get the object whose id is <id>
	 * 
	 * @param id
	 * @return
	 */
	public static Object getObject(int id) {
		return sharedObjectsMap.get(id);
	}
}
